// Scan organ keyboard with Arduino Leonardo board (double scan for contact bounce supression) // Using MIDIUSB library v.1.0.5 from Gary Grewal (install from the Arduino IDE Library Manaager) // John Harvey 7 May 2023 // www.johnharvey.uk // See these for more information: // www.arduino.cc/en/Reference/MIDIUSB // www.arduino.cc/en/Tutorial/MidiDevice // github.com/arduino-libraries/MIDIUSB // github.com/arduino/tutorials/blob/master/ArduinoZeroMidi/ArduinoZeroMidi.ino // Code is laid out in line for ease of understanding how it works; reduction in overall size could // be achieved using functions to encapsulate repeated code segments but with some loss of clarity #include // Arduino output pins to drive keyboard matrix rows const byte Row1 = 8; const byte Row2 = 9; const byte Row3 = 10; const byte Row4 = 11; const byte Row5 = A0; // Analog pins used as digital outputs const byte Row6 = A1; const byte Row7 = A2; const byte Row8 = A3; // Arduino input pins to read keyboard matrix columns const byte Column1 = 0; const byte Column2 = 1; const byte Column3 = 2; const byte Column4 = 3; const byte Column5 = 4; const byte Column6 = 5; const byte Column7 = 6; const byte Column8 = 7; const byte ScopeSync = 12; // Trigger oscilloscope at start of loop const byte MidiChannel = 1; // The channel assigned to this keyboard; channels are zero-based in code, so channel 1 is 0 etc. // Remember key state, true = key on, false = key off; only 61 keys in full 5 octave keyboard (and some have fewer keys) // but zero-based row/column scan of 8x8 keyboard matrix goes from 0 to 63 boolean KeyState[64]; // Previous key state boolean KeyState1[64]; // Data from first scan boolean KeyState2[64]; // Data from second scan void setup() { pinMode (Column1, INPUT_PULLUP); pinMode (Column2, INPUT_PULLUP); pinMode (Column3, INPUT_PULLUP); pinMode (Column4, INPUT_PULLUP); pinMode (Column5, INPUT_PULLUP); pinMode (Column6, INPUT_PULLUP); pinMode (Column7, INPUT_PULLUP); pinMode (Column8, INPUT_PULLUP); pinMode (Row1, OUTPUT); pinMode (Row2, OUTPUT); pinMode (Row3, OUTPUT); pinMode (Row4, OUTPUT); pinMode (Row5, OUTPUT); pinMode (Row6, OUTPUT); pinMode (Row7, OUTPUT); pinMode (Row8, OUTPUT); pinMode (A4, OUTPUT); // Set unused analog inputs as outputs so that stray noise does not trigger ADCs pinMode (A5, OUTPUT); digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); pinMode(LED_BUILTIN, OUTPUT); // Pin 13 is LED_BUILTIN, use for bottom C visual indication digitalWrite(LED_BUILTIN, LOW); // Set LED to off pinMode (ScopeSync, OUTPUT); digitalWrite(ScopeSync, LOW); for (byte keyNumber = 0; keyNumber <= 63; keyNumber ++) { KeyState[keyNumber] = false; // KeyState1[keyNumber] = false; // All notes off KeyState2[keyNumber] = false; // } } void loop() { digitalWrite(ScopeSync, HIGH); // Generate oscilloscope sync pulse at start of each keyboard scan for test purposes delayMicroseconds(50); digitalWrite(ScopeSync, LOW); // FIRST SCAN for (byte row = 1; row <= 8; row ++) { // Rows are normally high, set one low at a time and see which columns are pulled low by key presses if (row == 1) { digitalWrite(Row1, LOW); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 2) { digitalWrite(Row1, HIGH); digitalWrite(Row2, LOW); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 3) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, LOW); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 4) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, LOW); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 5) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, LOW); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 6) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, LOW); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 7) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, LOW); digitalWrite(Row8, HIGH); } if (row == 8) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, LOW); } delayMicroseconds(100); byte keyNumber; for (byte col = 1; col <= 8; col ++) { keyNumber = ((row - 1) * 8) + (col - 1); KeyState1[keyNumber] = !digitalRead(col); // Low = key on, so invert } } delay(1); // Milliseconds; increase delay here for more agressive contact debounce // SECOND SCAN for (byte row = 1; row <= 8; row ++) { if (row == 1) { digitalWrite(Row1, LOW); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 2) { digitalWrite(Row1, HIGH); digitalWrite(Row2, LOW); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 3) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, LOW); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 4) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, LOW); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 5) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, LOW); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 6) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, LOW); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 7) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, LOW); digitalWrite(Row8, HIGH); } if (row == 8) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, LOW); } delayMicroseconds(100); byte keyNumber; for (byte col = 1; col <= 8; col ++) { keyNumber = ((row - 1) * 8) + (col - 1); KeyState2[keyNumber] = !digitalRead(col); // Low = key on, so invert } } // Now process collected key data from both scans, comparing time-separated data for each key boolean PreviousKeyState; for (byte keyNumber = 0; keyNumber <= 63; keyNumber ++) { PreviousKeyState = KeyState[keyNumber]; // Note that "keyNumber + 0x24" etc. produces an int in compiler so reconvert to byte value otherwise compiler outputs a narrowing conversion warning if (PreviousKeyState == false && KeyState1[keyNumber] == true && KeyState2[keyNumber] == true) { // Note has just gone on KeyState[keyNumber] = true; midiEventPacket_t noteOn = {0x09, byte(MidiChannel - 1 + 0x90), byte(keyNumber + 0x24), 0x40}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); if (keyNumber == 0) { digitalWrite(LED_BUILTIN, HIGH); // Turn Arduino onboard LED on (Bottom C visual indication) } } if (PreviousKeyState == true && KeyState1[keyNumber] == false && KeyState2[keyNumber] == false) { // Note has just gone off KeyState[keyNumber] = false; midiEventPacket_t noteOn = {0x08, byte(MidiChannel - 1 + 0x80), byte(keyNumber + 0x24), 0x40}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); if (keyNumber == 0) { digitalWrite(LED_BUILTIN, LOW); // Turn Arduino onboard LED off } } } delayMicroseconds(100); }